using System.Diagnostics;
using System.Collections.Generic;
using System.Text;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.store
{


///
/// <summary> * Implementation of the relationship type store. Uses a dynamic store to store
/// * relationship type names. </summary>
/// 
	public class RelationshipTypeStore : AbstractStore, Store
	{
	// store version, each store ends with this string (byte encoded)
		private const string VERSION = "RelationshipTypeStore v0.9.5";

	// record header size
	// in_use(byte)+type_blockId(int)
		private const int RECORD_SIZE = 5;

		private const int TYPE_STORE_BLOCK_SIZE = 30;

		private DynamicStringStore typeNameStore;

///    
///     <summary> * See <seealso cref="AbstractStore#AbstractStore(String, Map)"/> </summary>
///     
		public RelationshipTypeStore<T1>(string fileName, Map<T1> config) : base(fileName, config)
		{
		}

///    
///     <summary> * See <seealso cref="AbstractStore#AbstractStore(String)"/> </summary>
///     
		public RelationshipTypeStore(string fileName) : base(fileName)
		{
		}

		protected internal override void setRecovered()
		{
			base.setRecovered();
			typeNameStore.setRecovered();
		}

		protected internal override void unsetRecovered()
		{
			base.unsetRecovered();
			typeNameStore.unsetRecovered();
		}

		protected internal override void initStorage()
		{
			typeNameStore = new DynamicStringStore(getStorageFileName() + ".names", getConfig());
		}

		protected internal override void closeStorage()
		{
			typeNameStore.Close();
			typeNameStore = null;
		}

		public virtual void flushAll()
		{
			typeNameStore.flushAll();
			base.flushAll();
		}

		public virtual string getTypeAndVersionDescriptor()
		{
			return VERSION;
		}

		public override int getRecordSize()
		{
			return RECORD_SIZE;
		}

///    
///     <summary> * Creates a new relationship type store contained in <CODE>fileName</CODE>
///     * If filename is <CODE>null</CODE> or the file already exists an 
///     * <CODE>IOException</CODE> is thrown.
///     *  </summary>
///     * <param name="fileName">
///     *            File name of the new relationship type store </param>
///     * <exception cref="IOException">
///     *             If unable to create store or name null </exception>
///     
		public static void createStore(string fileName)
		{
			createEmptyStore(fileName, VERSION);
			DynamicStringStore.createStore(fileName + ".names", TYPE_STORE_BLOCK_SIZE);
			RelationshipTypeStore store = new RelationshipTypeStore(fileName);
			store.markAsReserved(store.nextId());
			store.markAsReserved(store.nextId());
			store.markAsReserved(store.nextId());
			store.Close();
		}

		internal virtual void markAsReserved(int id)
		{
			PersistenceWindow window = acquireWindow(id, OperationType.WRITE);
			try
			{
				markAsReserved(id, window);
			}
			finally
			{
				releaseWindow(window);
			}
		}

		public virtual Collection<DynamicRecord> allocateTypeNameRecords(int startBlock, char[] src)
		{
			return typeNameStore.allocateRecords(startBlock, src);
		}

		public virtual void updateRecord(RelationshipTypeRecord record, bool recovered)
		{
			Debug.Assert(recovered);
			setRecovered();
			try
			{
				updateRecord(record);
			}
			finally
			{
				unsetRecovered();
			}
		}

		public virtual void updateRecord(RelationshipTypeRecord record)
		{
			PersistenceWindow window = acquireWindow(record.getId(), OperationType.WRITE);
			try
			{
				updateRecord(record, window);
			}
			finally
			{
				releaseWindow(window);
			}
			foreach (DynamicRecord typeRecord in record.getTypeRecords())
			{
				typeNameStore.updateRecord(typeRecord);
			}
		}

		public virtual RelationshipTypeRecord getRecord(int id)
		{
			RelationshipTypeRecord record;
			PersistenceWindow window = acquireWindow(id, OperationType.READ);
			try
			{
				record = getRecord(id, window);
			}
			finally
			{
				releaseWindow(window);
			}
		// }
			if (record != null)
			{
				Collection<DynamicRecord> nameRecords = typeNameStore.getRecords(record.getTypeBlock());
				foreach (DynamicRecord nameRecord in nameRecords)
				{
					record.addTypeRecord(nameRecord);
				}
			}
			return record;
		}

		public virtual RelationshipTypeData getRelationshipType(int id, bool recovered)
		{
			Debug.Assert(recovered);
			try
			{
				setRecovered();
				RelationshipTypeRecord record = getRecord(id);
				string name = getStringFor(record);
				return new RelationshipTypeData(id, name);
			}
			finally
			{
				unsetRecovered();
			}
		}

		public virtual RelationshipTypeData getRelationshipType(int id)
		{
			RelationshipTypeRecord record = getRecord(id);
			string name = getStringFor(record);
			return new RelationshipTypeData(id, name);
		}

		public virtual RelationshipTypeData[] getRelationshipTypes()
		{
			LinkedList<RelationshipTypeData> typeDataList = new LinkedList<RelationshipTypeData>();
			for (int i = 0;; i++)
			{
				RelationshipTypeRecord record;
				try
				{
					record = getRecord(i);
				}
				catch (InvalidRecordException e)
				{
					break;
				}
				if (record != null && record.getTypeBlock() != Record.RESERVED.intValue())
				{
					string name = getStringFor(record);
					typeDataList.AddLast(new RelationshipTypeData(i, name));
				}
			}
			return typeDataList.ToArray(new RelationshipTypeData[typeDataList.Count]);
		}

		public virtual int nextBlockId()
		{
			return typeNameStore.nextBlockId();
		}

		public virtual void freeBlockId(int id)
		{
			typeNameStore.freeBlockId(id);
		}

		private void markAsReserved(int id, PersistenceWindow window)
		{
			Buffer buffer = window.getOffsettedBuffer(id);
			buffer.put(Record.IN_USE.byteValue()).putInt(Record.RESERVED.intValue());
		}

		private RelationshipTypeRecord getRecord(int id, PersistenceWindow window)
		{
			Buffer buffer = window.getOffsettedBuffer(id);
			sbyte inUse = buffer.get();
			if (inUse == Record.NOT_IN_USE.byteValue())
			{
				return null;
			}
			if (inUse != Record.IN_USE.byteValue())
			{
				throw new InvalidRecordException("Record[" + id + "] unknown in use flag[" + inUse + "]");
			}
			RelationshipTypeRecord record = new RelationshipTypeRecord(id);
			record.setInUse(true);
			record.setTypeBlock(buffer.getInt());
			return record;
		}

		private void updateRecord(RelationshipTypeRecord record, PersistenceWindow window)
		{
			int id = record.getId();
			Buffer buffer = window.getOffsettedBuffer(id);
			if (record.inUse())
			{
				buffer.put(Record.IN_USE.byteValue()).putInt(record.getTypeBlock());
			}
			else
			{
				buffer.put(Record.NOT_IN_USE.byteValue()).putInt(0);
			}
		}

		protected internal override void rebuildIdGenerator()
		{
			logger.fine("Rebuilding id generator for[" + getStorageFileName() + "] ...");
			closeIdGenerator();
			File file = new File(getStorageFileName() + ".id");
			if (file.exists())
			{
				bool success = file.delete();
				Debug.Assert(success);
			}
			IdGeneratorImpl.createGenerator(getStorageFileName() + ".id");
			openIdGenerator();
			FileChannel fileChannel = getFileChannel();
			long highId = -1;
			int recordSize = getRecordSize();
			try
			{
				long fileSize = fileChannel.size();
				ByteBuffer byteBuffer = ByteBuffer.wrap(new sbyte[recordSize]);
				for (int i = 0; i * recordSize < fileSize; i++)
				{
					fileChannel.read(byteBuffer, i * recordSize);
					byteBuffer.flip();
					sbyte inUse = byteBuffer.get();
					byteBuffer.flip();
					if (inUse != Record.IN_USE.byteValue())
					{
					// hole found, marking as reserved
						byteBuffer.Clear();
						byteBuffer.put(Record.IN_USE.byteValue()).putInt(Record.RESERVED.intValue());
						byteBuffer.flip();
						fileChannel.write(byteBuffer, i * recordSize);
						byteBuffer.Clear();
					}
					else
					{
						highId = i;
					}
					nextId();
				}
				highId++;
				fileChannel.truncate(highId * recordSize);
			}
			catch (IOException e)
			{
				throw new UnderlyingStorageException("Unable to rebuild id generator " + getStorageFileName(), e);
			}
			setHighId(highId);
			logger.fine("[" + getStorageFileName() + "] high id=" + getHighId());
			closeIdGenerator();
			openIdGenerator();
		}

		public virtual string getStringFor(RelationshipTypeRecord relTypeRecord)
		{
			int recordToFind = relTypeRecord.getTypeBlock();
			IEnumerator <DynamicRecord> records = relTypeRecord.getTypeRecords().GetEnumerator();
			List<char[]> charList = new LinkedList<char[]>();
			int totalSize = 0;
			while (recordToFind != Record.NO_NEXT_BLOCK.intValue() && records.MoveNext())
			{
				DynamicRecord record = records.Current;
				if (record.inUse() && record.getId() == recordToFind)
				{
					if (record.isLight())
					{
						typeNameStore.makeHeavy(record);
					}
					if (!record.isCharData())
					{
						ByteBuffer buf = ByteBuffer.wrap(record.getData());
						char[] chars = new char[record.getData().length / 2];
						totalSize += chars.Length;
						buf.asCharBuffer().get(chars);
						charList.Add(chars);
					}
					else
					{
						charList.Add(record.getDataAsChar());
					}
					recordToFind = record.getNextBlock();
				// TODO: optimize here, high chance next is right one
					records = relTypeRecord.getTypeRecords().GetEnumerator();
				}
			}
			StringBuilder buf = new StringBuilder();
			foreach (char[] str in charList)
			{
				buf.Append(str);
			}
			return buf.ToString();
		}

		public override void makeStoreOk()
		{
			typeNameStore.makeStoreOk();
			base.makeStoreOk();
		}

		public override void rebuildIdGenerators()
		{
			typeNameStore.rebuildIdGenerators();
			base.rebuildIdGenerators();
		}

		public virtual void updateIdGenerators()
		{
			typeNameStore.updateHighId();
			this.updateHighId();
		}

		protected internal override bool versionFound(string version)
		{
			if (!version.StartsWith("RelationshipTypeStore"))
			{
			// non clean shutdown, need to do recover with right neo
				return false;
			}
			if (version.Equals("RelationshipTypeStore v0.9.3"))
			{
				rebuildIdGenerator();
				closeIdGenerator();
				return true;
			}
			throw new IllegalStoreVersionException("Store version [" + version + "]. Please make sure you are not running old Neo4j kernel " + " towards a store that has been created by newer version " + " of Neo4j.");
		}
	}
}